<script lang="tsx">
import type { PropType, Ref } from 'vue'
import { computed, defineComponent, toRefs, unref } from 'vue'
import type { Rule as ValidationRule } from 'ant-design-vue/lib/form'
import { Col, Divider, Form } from 'ant-design-vue'
import { cloneDeep, upperFirst } from 'lodash-es'
import type { FormActionType, FormProps, FormSchemaInner as FormSchema } from '../types/form'
import { isComponentFormSchema } from '../types/form'
import { componentMap } from '../componentMap'
import { NO_AUTO_LINK_COMPONENTS, createPlaceholderMessage, setComponentRuleType } from '../helper'
import { useItemLabelWidth } from '../hooks/useLabelWidth'
import type { TableActionType } from '@/components/Table'
import { BasicHelp } from '@/components/Basic'
import { isBoolean, isFunction, isNull } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper'
import type { Nullable, Recordable } from '@/utils/types'
import { useI18n } from '@/hooks/web/useI18n'

export default defineComponent({
  name: 'BasicFormItem',
  inheritAttrs: false,
  props: {
    schema: {
      type: Object as PropType<FormSchema>,
      default: () => ({}),
    },
    formProps: {
      type: Object as PropType<FormProps>,
      default: () => ({}),
    },
    allDefaultValues: {
      type: Object as PropType<Recordable<any>>,
      default: () => ({}),
    },
    formModel: {
      type: Object as PropType<Recordable<any>>,
      default: () => ({}),
    },
    setFormModel: {
      type: Function as PropType<(key: string, value: any, schema: FormSchema) => void>,
      default: null,
    },
    tableAction: {
      type: Object as PropType<TableActionType>,
    },
    formActionType: {
      type: Object as PropType<FormActionType>,
    },
    isAdvanced: {
      type: Boolean,
    },
  },
  setup(props, { slots }) {
    const { t } = useI18n()

    const { schema, formProps } = toRefs(props) as {
      schema: Ref<FormSchema>
      formProps: Ref<FormProps>
    }

    const itemLabelWidthProp = useItemLabelWidth(schema, formProps)

    const getValues = computed(() => {
      const { allDefaultValues, formModel, schema } = props
      const { mergeDynamicData } = props.formProps
      return {
        field: schema.field,
        model: formModel,
        values: {
          ...mergeDynamicData,
          ...allDefaultValues,
          ...formModel,
        } as Recordable<any>,
        schema,
      }
    })

    const getComponentsProps = computed(() => {
      const { schema, tableAction, formModel, formActionType } = props
      let { componentProps = {} } = schema
      if (isFunction(componentProps))
        componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {}

      if (schema.component === 'Divider') {
        componentProps = Object.assign(
          { type: 'horizontal' },
          {
            orientation: 'left',
            plain: true,
          },
          componentProps,
        )
      }
      return componentProps as Recordable<any>
    })

    const getDisable = computed(() => {
      const { disabled: globDisabled } = props.formProps
      const { dynamicDisabled } = props.schema
      const { disabled: itemDisabled = false } = unref(getComponentsProps)
      let disabled = !!globDisabled || itemDisabled
      if (isBoolean(dynamicDisabled))
        disabled = dynamicDisabled

      if (isFunction(dynamicDisabled))
        disabled = dynamicDisabled(unref(getValues))

      return disabled
    })

    function getShow(): { isShow: boolean; isIfShow: boolean } {
      const { show, ifShow } = props.schema
      const { showAdvancedButton } = props.formProps
      const itemIsAdvanced = showAdvancedButton
        ? isBoolean(props.isAdvanced)
          ? props.isAdvanced
          : true
        : true

      let isShow = true
      let isIfShow = true

      if (isBoolean(show))
        isShow = show

      if (isBoolean(ifShow))
        isIfShow = ifShow

      if (isFunction(show))
        isShow = show(unref(getValues))

      if (isFunction(ifShow))
        isIfShow = ifShow(unref(getValues))

      isShow = isShow && itemIsAdvanced
      return { isShow, isIfShow }
    }

    function handleRules(): ValidationRule[] {
      const {
        rules: defRules = [],
        component,
        rulesMessageJoinLabel,
        label,
        dynamicRules,
        required,
      } = props.schema

      if (isFunction(dynamicRules))
        return dynamicRules(unref(getValues)) as ValidationRule[]

      let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]
      const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps

      const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
        ? rulesMessageJoinLabel
        : globalRulesMessageJoinLabel
      const assertLabel = joinLabel ? label : ''
      const defaultMsg = component
        ? createPlaceholderMessage(component) + assertLabel
        : assertLabel

      function validator(rule: any, value: any) {
        const msg = rule.message || defaultMsg
        if (value === undefined || isNull(value)) {
          // 空值
          return Promise.reject(msg)
        }
        else if (Array.isArray(value) && value.length === 0) {
          // 数组类型
          return Promise.reject(msg)
        }
        else if (typeof value === 'string' && value.trim() === '') {
          // 空字符串
          return Promise.reject(msg)
        }
        else if (
          typeof value === 'object'
          && Reflect.has(value, 'checked')
          && Reflect.has(value, 'halfChecked')
          && Array.isArray(value.checked)
          && Array.isArray(value.halfChecked)
          && value.checked.length === 0
          && value.halfChecked.length === 0
        ) {
          // 非关联选择的tree组件
          return Promise.reject(msg)
        }
        return Promise.resolve()
      }

      const getRequired = isFunction(required) ? required(unref(getValues)) : required

      /*
       * 1、若设置了required属性，又没有其他的rules，就创建一个验证规则；
       * 2、若设置了required属性，又存在其他的rules，则只rules中不存在required属性时，才添加验证required的规则
       *     也就是说rules中的required，优先级大于required
       */
      if (getRequired) {
        if (!rules || rules.length === 0) {
          rules = [{ required: getRequired, validator }]
        }
        else {
          const requiredIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required'))

          if (requiredIndex === -1)
            rules.push({ required: getRequired, validator })
        }
      }

      const requiredRuleIndex: number = rules.findIndex(
        rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
      )

      if (requiredRuleIndex !== -1) {
        const rule = rules[requiredRuleIndex]
        const { isShow } = getShow()
        if (!isShow)
          rule.required = false

        if (component) {
          rule.message = rule.message || defaultMsg

          if (component.includes('Input') || component.includes('Textarea'))
            rule.whitespace = true

          const valueFormat = unref(getComponentsProps)?.valueFormat
          setComponentRuleType(rule, component, valueFormat)
        }
      }

      // Maximum input length rule check
      const characterInx = rules.findIndex(val => val.max)
      if (characterInx !== -1 && !rules[characterInx].validator) {
        rules[characterInx].message
          = rules[characterInx].message
          || t('component.form.maxTip', [rules[characterInx].max] as Recordable<any>)
      }
      return rules
    }

    function renderComponent() {
      if (!isComponentFormSchema(props.schema))
        return null

      const {
        renderComponentContent,
        component,
        field,
        changeEvent = 'change',
        valueField,
      } = props.schema

      const isCheck = component && ['Switch', 'Checkbox'].includes(component)

      const eventKey = `on${upperFirst(changeEvent)}`

      const { autoSetPlaceHolder, size } = props.formProps
      const propsData: Recordable<any> = {
        allowClear: true,
        size,
        ...unref(getComponentsProps),
        disabled: unref(getDisable),
      }

      const on = {
        [eventKey]: (...args: Nullable<Recordable<any>>[]) => {
          const [e] = args
          if (propsData[eventKey])
            propsData[eventKey](...args)

          const target = e ? e.target : null
          const value = target ? (isCheck ? target.checked : target.value) : e
          props.setFormModel(field, value, props.schema)
        },
      }
      const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>

      const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder
      // RangePicker place is an array
      if (isCreatePlaceholder && component !== 'RangePicker' && component) {
        propsData.placeholder
          = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component)
      }
      propsData.codeField = field
      propsData.formValues = unref(getValues)

      const bindValue: Recordable<any> = {
        [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
      }

      const compAttr: Recordable<any> = {
        ...propsData,
        ...on,
        ...bindValue,
      }

      if (!renderComponentContent)
        return <Comp {...compAttr} />

      const compSlot = isFunction(renderComponentContent)
        ? { ...renderComponentContent(unref(getValues), { disabled: unref(getDisable) }) }
        : {
            default: () => renderComponentContent,
          }
      return <Comp {...compAttr}>{compSlot}</Comp>
    }

    function renderLabelHelpMessage() {
      const { label, helpMessage, helpComponentProps, subLabel } = props.schema
      const renderLabel = subLabel
        ? (
          <span>
            {label}
            {' '}
            <span class="text-secondary">{subLabel}</span>
          </span>
          )
        : (
            label
          )
      const getHelpMessage = isFunction(helpMessage)
        ? helpMessage(unref(getValues))
        : helpMessage
      if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0))
        return renderLabel

      return (
        <span>
          {renderLabel}
          <BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
        </span>
      )
    }

    function renderItem() {
      const { itemProps, slot, render, field, suffix, component } = props.schema
      const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
      const { colon } = props.formProps
      const opts = { disabled: unref(getDisable) }
      if (component === 'Divider') {
        return (
          <Col span={24}>
            <Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
          </Col>
        )
      }
      else {
        const getContent = () => {
          return slot
            ? getSlot(slots, slot, unref(getValues), opts)
            : render
              ? render(unref(getValues), opts)
              : renderComponent()
        }

        const showSuffix = !!suffix
        const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix

        // TODO 自定义组件验证会出现问题，因此这里框架默认将自定义组件设置手动触发验证，如果其他组件还有此问题请手动设置autoLink=false
        if (component && NO_AUTO_LINK_COMPONENTS.includes(component)) {
          props.schema
            && (props.schema.itemProps! = {
              autoLink: false,
              ...props.schema.itemProps,
            })
        }

        return (
          <Form.Item
            name={field}
            colon={colon}
            class={{ 'suffix-item': showSuffix }}
            {...(itemProps as Recordable<any>)}
            label={renderLabelHelpMessage()}
            rules={handleRules()}
            labelCol={labelCol}
            wrapperCol={wrapperCol}
          >
            <div style="display:flex">
              <div style="flex:1;">{getContent()}</div>
              {showSuffix && <span class="suffix">{getSuffix}</span>}
            </div>
          </Form.Item>
        )
      }
    }

    return () => {
      const { colProps = {}, colSlot, renderColContent, component, slot } = props.schema
      if (!component || (!componentMap.has(component) && !slot))
        return null

      const { baseColProps = {} } = props.formProps
      const realColProps = { ...baseColProps, ...colProps }
      const { isIfShow, isShow } = getShow()
      const values = unref(getValues)
      const opts = { disabled: unref(getDisable) }

      const getContent = () => {
        return colSlot
          ? getSlot(slots, colSlot, values, opts)
          : renderColContent
            ? renderColContent(values, opts)
            : renderItem()
      }

      return (
        isIfShow && (
          <Col {...realColProps} v-show={isShow}>
            {getContent()}
          </Col>
        )
      )
    }
  },
})
</script>
